Skip to content

fix(core): strip virtual fields from include before Prisma call#633

Merged
borisno2 merged 1 commit into
mainfrom
claude/issue-628-tr5hi3
Jul 10, 2026
Merged

fix(core): strip virtual fields from include before Prisma call#633
borisno2 merged 1 commit into
mainfrom
claude/issue-628-tr5hi3

Conversation

@borisno2

Copy link
Copy Markdown
Member

Summary

  • Virtual fields named in a caller-supplied include (e.g. context.db.myList.findMany({ include: { myVirtualField: true } })) used to be forwarded straight through to Prisma, which threw Unknown field '<name>' for include statement because virtual fields have no database column.
  • Adds stripVirtualFieldsFromInclude() in packages/core/src/access/access-filter.ts, which recursively removes virtual-field keys from a Prisma include object while preserving real relationship includes (and their nested access-control filters) at every level.
  • Applied as the final step in context/index.ts for both findUnique and findMany, after include is resolved via the fragment path, the access-controlled merge path, or the sudo passthrough path — so a virtual key never reaches the Prisma client regardless of which path produced it.
  • The virtual field's value is unaffected: filterReadableFields already computes it unconditionally via resolveOutput, whether or not the field was named in include.

Test plan

  • Regression tests added in packages/core/tests/context.test.ts (virtual fields named in include no longer reach Prisma (#628)):
    • findMany/findUnique with a virtual field alongside a real relationship in include — the virtual key is stripped from the Prisma call, the relationship include is untouched, and the virtual value is still returned.
    • The virtual value is populated even when omitted from include.
    • Sudo path also strips the virtual key (while leaving the real relationship passthrough unfiltered, matching existing sudo behaviour).
    • Read access control on a virtual field is still enforced (a field denied by read access is omitted from the result).
  • pnpm test — full packages/core suite passes (729 tests)
  • pnpm build (tsc) passes
  • pnpm lint — no new warnings/errors
  • pnpm format / pnpm manypkg fix — no changes needed beyond the diff
  • Changeset added (@opensaas/stack-core: patch)

Closes #628


Generated by Claude Code

Virtual field keys named in `include`/`select` used to be forwarded
straight through to Prisma, which threw "Unknown field" since virtual
fields have no database column. The read path now strips virtual keys
from the include payload (fragment, access-controlled merge, and sudo
passthrough) while the value is still computed via resolveOutput
regardless of whether the field was named.

Closes #628

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6fWyGeMiexwmQLD2usMcn
@changeset-bot

changeset-bot Bot commented Jul 10, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: bb84ccc

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
@opensaas/stack-core Patch
@opensaas/stack-auth Patch
@opensaas/stack-cli Patch
@opensaas/stack-rag Patch
@opensaas/stack-storage Patch
@opensaas/stack-tiptap Patch
@opensaas/stack-ui Patch
@opensaas/stack-storage-s3 Patch
@opensaas/stack-storage-vercel Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Jul 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
stack-docs Ready Ready Preview, Comment Jul 10, 2026 1:58am

@borisno2 borisno2 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Overview

Fixes #628: a virtual field named in a caller-supplied include (e.g. context.db.myList.findMany({ include: { myVirtualField: true } })) used to be forwarded straight to Prisma and throw Unknown field at runtime, since virtual fields have no database column. This PR adds stripVirtualFieldsFromInclude() (in access-filter.ts) and applies it as the final step before model.findFirst/model.findMany in context/index.ts, covering all three paths that can produce include (fragment-built, access-controlled merge, sudo passthrough).

Correctness

  • Core logic is sound. The strip check (fieldConfig?.virtual) runs before the relationship check, so a virtual field is dropped regardless of whether its value is bare true or an object — matches all the ways a caller could name it.
  • Nested relation includes preserve non-include keys correctly. The recursive branch spreads the original value (not the narrowed entry from asEntryObject) and only overrides include, so where/orderBy/take/skip/select on a relation entry survive untouched.
  • findFirst is covered for free since it delegates to the fixed findMany. createGet (singleton) doesn't accept a caller include and its auto-built include only ever contains relationship keys, so it's correctly left untouched.
  • The sudo passthrough fix is a real, distinct improvement — previously sudo used args.include completely unfiltered, so this bug applied there too. Good that it's covered by both the fix and a dedicated test.

Test coverage

Five new regression tests in context.test.ts under virtual fields named in include no longer reach Prisma (#628):

  • findMany/findUnique with a virtual field + real relationship in include → virtual key stripped, relationship include intact, virtual value still returned.
  • Virtual value populated when omitted from include (pre-existing behavior, now guarded against regression).
  • Sudo path strips the virtual key while leaving the real relationship passthrough unfiltered (matches documented sudo semantics).
  • Read access control on a virtual field still applies.

This satisfies all the acceptance criteria from the issue's triage brief. Full suite (729 tests), tsc build, and lint all pass with no regressions.

Minor/non-blocking observations

  1. Unresolvable related config skips nested stripping. If a declared relationship's ref can't be resolved via getRelatedListConfig (unlikely — config validation should prevent a dangling ref), the nested include under that relation is passed through without recursing. This mirrors an existing fallback pattern elsewhere in the codebase (e.g. filterReadableFields's "Related config not found, include the value as-is" branch), so it's consistent rather than a new gap.
  2. Nested select inside a relation include entry (Prisma also supports { include: { posts: { select: {...} } } }) isn't scanned for virtual keys. Out of scope per the issue — worth a follow-up only if that usage becomes common.

Security

No concerns — this only narrows what reaches Prisma; it doesn't loosen any access control path. Field-level read access on virtual fields and relationship-level access filtering both remain intact and tested.

Verdict

Solid, minimally-scoped fix with good test coverage matching the issue's acceptance criteria. No blocking issues found. Approving.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for Core Package Coverage (./packages/core)

Status Category Percentage Covered / Total
🔵 Lines 91.98% (🎯 65%) 872 / 948
🔵 Statements 91.27% (🎯 65%) 910 / 997
🔵 Functions 97.97% (🎯 62%) 145 / 148
🔵 Branches 80.21% (🎯 50%) 588 / 733
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/core/src/access/access-filter.ts 100% 91.02% 100% 100%
Generated in workflow #1225 for commit bb84ccc by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for UI Package Coverage (./packages/ui)

Status Category Percentage Covered / Total
🔵 Lines 76.03% 92 / 121
🔵 Statements 75.39% 95 / 126
🔵 Functions 75.6% 31 / 41
🔵 Branches 65.78% 75 / 114
File CoverageNo changed files found.
Generated in workflow #1225 for commit bb84ccc by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for CLI Package Coverage (./packages/cli)

Status Category Percentage Covered / Total
🔵 Lines 79.21% 1490 / 1881
🔵 Statements 78.92% 1550 / 1964
🔵 Functions 84.45% 201 / 238
🔵 Branches 67.25% 653 / 971
File CoverageNo changed files found.
Generated in workflow #1225 for commit bb84ccc by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for Auth Package Coverage (./packages/auth)

Status Category Percentage Covered / Total
🔵 Lines 74.64% 159 / 213
🔵 Statements 69.74% 166 / 238
🔵 Functions 83.11% 64 / 77
🔵 Branches 70.67% 94 / 133
File CoverageNo changed files found.
Generated in workflow #1225 for commit bb84ccc by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for Storage Package Coverage (./packages/storage)

Status Category Percentage Covered / Total
🔵 Lines 74.8% 190 / 254
🔵 Statements 76.44% 211 / 276
🔵 Functions 85.89% 67 / 78
🔵 Branches 70.73% 174 / 246
File CoverageNo changed files found.
Generated in workflow #1225 for commit bb84ccc by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for RAG Package Coverage (./packages/rag)

Status Category Percentage Covered / Total
🔵 Lines 47.97% 355 / 740
🔵 Statements 48.14% 377 / 783
🔵 Functions 54.26% 70 / 129
🔵 Branches 42.55% 180 / 423
File CoverageNo changed files found.
Generated in workflow #1225 for commit bb84ccc by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for Storage S3 Package Coverage (./packages/storage-s3)

Status Category Percentage Covered / Total
🔵 Lines 100% 40 / 40
🔵 Statements 100% 40 / 40
🔵 Functions 100% 9 / 9
🔵 Branches 100% 19 / 19
File CoverageNo changed files found.
Generated in workflow #1225 for commit bb84ccc by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for Storage Vercel Package Coverage (./packages/storage-vercel)

Status Category Percentage Covered / Total
🔵 Lines 100% 38 / 38
🔵 Statements 100% 38 / 38
🔵 Functions 100% 8 / 8
🔵 Branches 100% 22 / 22
File CoverageNo changed files found.
Generated in workflow #1225 for commit bb84ccc by the Vitest Coverage Report Action

@borisno2 borisno2 merged commit 9d9c7f8 into main Jul 10, 2026
6 checks passed
@borisno2 borisno2 deleted the claude/issue-628-tr5hi3 branch July 10, 2026 06:25
@github-actions github-actions Bot mentioned this pull request Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Virtual fields in include blocks cause runtime error: "Unknown field" from Prisma

2 participants